namespace ScrumSprintMonitor.ServerAdapter.TFS
{
	#region #using Directives

	using System;
	using System.Collections.Generic;
	using System.ComponentModel;
	using System.Linq;
	using System.Security;
	using System.Text;
	using System.Threading;

	using Infrastructure;
	using Infrastructure.Configuration;

	using Isolation.TeamFoundationServer;

	using Ninject.Core;
	using Ninject.Core.Parameters;

	#endregion

	public class TfsConnection : IConnection
	{
		private readonly IKernel kernel;
		private readonly ILogger<TfsConnection> logger;
		private readonly IProcessTemplateAdapterProvider processTemplateAdapterProvider;
		private readonly string serverName;
		private ITfsBuildServer buildServer;
		private bool disposed;
		private ITeamFoundationServer tfs;
		private ITfsWorkItemStore workItemStore;

		public TfsConnection(IKernel kernel, string serverName, IProcessTemplateAdapterProvider processTemplateAdapterProvider,
		                     ILogger<TfsConnection> logger)
		{
			this.kernel = kernel;
			this.serverName = serverName;
			this.processTemplateAdapterProvider = processTemplateAdapterProvider;
			this.logger = logger;
		}

		#region IConnection Members

		public bool IsDisposed
		{
			get { return this.disposed; }
		}

		public void Connect()
		{
			GuardDisposed();

			if (IsConnected)
			{
				throw new InvalidOperationException("The connection to the server is already established.");
			}

			lock (this)
			{
				if (!IsConnected)
				{
					this.logger.Debug("Connecting to TFS...");

					if (this.tfs == null)
					{
						this.tfs = new TeamFoundationServer(this.serverName);
					}

					Interlocked.Exchange(ref this.buildServer, this.tfs.GetBuildServer());
					Interlocked.Exchange(ref this.workItemStore, this.tfs.GetWorkItemStore());

					if (IsConnected)
					{
						OnPropertyChanged("IsConnected");
					}
				}
			}
		}

		public void Disconnect()
		{
			if (this.tfs != null)
			{
				this.logger.Debug("Disconnecting from TFS.");

				this.buildServer = null;
				this.workItemStore = null;
				this.tfs.Dispose();
				this.tfs = null;

				OnPropertyChanged("IsConnected");
			}
		}

		public IBuildInfo GetBuildInfo(ISprintConfiguration configuration)
		{
			GuardDisposed();

			if (configuration == null)
			{
				throw new ArgumentNullException("configuration");
			}
			if (this.buildServer == null)
			{
				return null;
			}

			this.logger.Debug("Getting build info.");

			return this.kernel.Get<IBuildInfo>(With.Parameters.ConstructorArgument("buildServer", this.buildServer));
		}

		public string GetSprintName(ISprintConfiguration configuration)
		{
			GuardDisposed();

			if (configuration == null)
			{
				throw new ArgumentNullException("configuration");
			}

			try
			{
				if (string.IsNullOrEmpty(configuration.IterationPath))
				{
					return this.workItemStore.GetIterationName(configuration.ProjectName, configuration.IterationId);
				}
				else
				{
					return this.workItemStore.GetIterationName(configuration.ProjectName, configuration.IterationPath);
				}
			}
			catch (TfsConnectionException e)
			{
				throw new ConnectionException(e);
			}
		}

		public Uri GetWorkItemDisplayUri(IWorkItemAdapter workItem)
		{
			GuardDisposed();

			this.logger.Debug("Getting work item display Uri for work item #{0}.", workItem.Id);

			return new Uri(this.tfs.Uri, string.Format("WorkItemTracking/WorkItem.aspx?artifactMoniker={0}", workItem.Id));
		}

		public IWorkItemStore GetWorkItemStore(ISprintConfiguration configuration)
		{
			GuardDisposed();

			if (configuration == null)
			{
				throw new ArgumentNullException("configuration");
			}
			if (!IsConnected)
			{
				throw new ConnectionException("The connection is not yet established");
			}

			var processTemplateAdapter = GetProcessTemplate(configuration);
			string tfsQueryString;

			if (string.IsNullOrEmpty(configuration.IterationPath))
			{
				this.logger.Debug("Getting work item store for {0}...", configuration.IterationId);

				tfsQueryString = this.workItemStore.GetQueryString(configuration.ProjectName, configuration.IterationId,
				                                                   GetWorkItemTypesQueryString(processTemplateAdapter.WorkItemTypeNames));
			}
			else
			{
				this.logger.Debug("Getting work item store for {0}...", configuration.IterationPath);

				tfsQueryString = this.workItemStore.GetQueryString(configuration.ProjectName, configuration.IterationPath,
				                                                   GetWorkItemTypesQueryString(processTemplateAdapter.WorkItemTypeNames));
			}

			var parameters = new Dictionary<string, object>
			                 	{
			                 		{"tfsWorkItemStore", this.workItemStore},
			                 		{"processTemplateAdapter", processTemplateAdapter},
			                 		{"tfsQueryString", tfsQueryString}
			                 	};
			return this.kernel.Get<IWorkItemStore>(With.Parameters.ConstructorArguments(parameters));
		}

		public ICollection<string> GetProjectList()
		{
			GuardDisposed();

			if (!IsConnected)
			{
				Connect();
			}

			this.logger.Debug("Getting project list...");

			return this.workItemStore.Projects;
		}

		public ICollection<string> GetBuildDefinitionList(string teamProject)
		{
			if (!IsConnected)
			{
				Connect();
			}

			this.logger.Debug("Getting build definition list for team project {0}.", teamProject);

			if (this.workItemStore.Projects.Contains(teamProject))
			{
				var buildDefinitions = this.buildServer.QueryBuildDefinitions(teamProject);

				return buildDefinitions;
			}

			this.logger.Debug("No project exists with name {0}.", teamProject);

			return null;
		}

		public ICollection<string> GetIterationPaths(string teamProject)
		{
			GuardDisposed();

			if (!IsConnected)
			{
				Connect();
			}

			this.logger.Debug("Getting iteration paths list for team project {0}.", teamProject);

			if (!this.workItemStore.Projects.Contains(teamProject))
			{
				this.logger.Debug("No project exists with name {0}.", teamProject);

				return null;
			}

			try
			{
				return this.tfs.GetIterationPaths(teamProject);
			}
			catch (SecurityException e)
			{
				this.logger.Info(e, "Security exception found while querying TFS for iteration paths.");

				return new List<string>();
			}
		}

		public bool IsConnected
		{
			get
			{
				GuardDisposed();

				return this.workItemStore != null;
			}
		}

		public void Dispose()
		{
			if (!this.disposed)
			{
				this.disposed = true;

				Disconnect();
			}
		}

		#endregion

		private void GuardDisposed()
		{
			if (this.disposed)
			{
				throw new ObjectDisposedException("TfsConnection");
			}
		}

		private IProcessTemplateAdapter GetProcessTemplate(ISprintConfiguration configuration)
		{
			try
			{
				return this.processTemplateAdapterProvider.AvailableProcessTemplates.First(x => x.ProcessTemplateName == configuration.ProcessTemplateName);
			}
			catch (InvalidOperationException e)
			{
				throw new ArgumentException(string.Format("'{0}' is not a recognized process template name.", configuration.ProcessTemplateName), "configuration",
				                            e);
			}
		}

		private static string GetWorkItemTypesQueryString(IEnumerable<string> workItemTypeNames)
		{
			var builder = new StringBuilder();

			foreach (string workItemTypeName in workItemTypeNames)
			{
				if (builder.Length > 0)
				{
					builder.Append(", ");
				}
				builder.AppendFormat("'{0}'", workItemTypeName);
			}

			return builder.ToString();
		}

		#region INotifyPropertyChanged members

		public event PropertyChangedEventHandler PropertyChanged;

		public void OnPropertyChanged(string propertyName)
		{
			if (PropertyChanged != null)
			{
				PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
			}
		}

		#endregion
	}
}